using System;
using System.Collections.Generic;
using System.Globalization;
using mshtml;

using PasswordProvider.ObjectModel;

namespace PasswordProvider.ViewModel.Helpers
{
    static class IEHtmlDocumentHelper
    {
        #region Public Methods
       
        /// <summary>
        /// Attempts to find the favicon - or "shortcut icon" - path.
        /// </summary>
        public static Uri FindFavIcon(HTMLDocument doc)
        {
            // Look for "shortcut icon" first
            foreach (IHTMLElement element in doc.getElementsByTagName("link"))
            {
                string rel = element.getAttribute("rel", 2) as string;
                if(!String.IsNullOrEmpty(rel))
                {
                    if( rel.Equals("shortcut icon", StringComparison.OrdinalIgnoreCase) )
                    {
                        string path = element.getAttribute("href", 2) as string;
                        if (!String.IsNullOrEmpty(path))
                        {
                            
                            return UriHelper.CreateUri(path, doc.url);
                        }
                    }
                }
            }
            // Look for "icon" second.
            foreach (IHTMLElement element in doc.getElementsByTagName("link"))
            {
                string rel = element.getAttribute("rel", 2) as string;
                if (!String.IsNullOrEmpty(rel))
                {
                    if (rel.Equals("icon", StringComparison.OrdinalIgnoreCase))
                    {
                        string path = element.getAttribute("href", 2) as string;
                        if (!String.IsNullOrEmpty(path))
                        {
                            return UriHelper.CreateUri(path);
                        }
                    }
                }
            }

            return null;
        }
 

        /// <summary>
        /// Attempt to fill the login and password fields of an arbitrary html document by searching for 
        /// input fields which have names and/or id that contain words that might be expected to be 
        /// part of the name or id of a password or login input field.
        /// </summary>
        public static bool AttemptFillPasswordAndLogin(HTMLDocument doc, string login, Guid siteGuid)
        {
            if (doc == null)
                throw new InvalidOperationException("html doc cannot be null");

            // login id
            bool success = SetInputElements(FindMatchingInputElement(doc, "log"), login);
            //if(!success)
            success = SetInputElements(FindMatchingInputElement(doc, "id"), login);
            //if (!success)
            success = SetInputElements(FindMatchingInputElement(doc, "name"), login);

            // password.
            List<IHTMLElement> list = FindMatchingInputElement(doc, "pass");
            if (list.Count > 0)
            {
                EncryptedValue encrypted = PasswordProviderData.Current.Sites[siteGuid].PrimaryEncryptedValue;
                ProtectedString ps = PasswordProviderData.Current.SecurityManager.GetValueDecrypted(encrypted);
                if(ps != null)   
                    success = SetInputElements(list, ps.UnprotectedString);                
            }
            else
                success = false;

            return success;
        }

        #endregion

        #region Private Methods

        // Sets the value of all input elements returned by method FindMatchingInputElement. 
        private static bool SetInputElements(List<IHTMLElement> elements, string value)
        {
            if (elements.Count == 0)
                return false;

            // return true if value is null, allows test run.
            if (String.IsNullOrEmpty(value))
                return true; 

            foreach (IHTMLElement element in elements)
            {
                element.setAttribute("value", value, 0);
            }
            return true;
        }

        // Attempt to find an input field in the html document which has a name or id which contians the tag name hint.        
        private static List<IHTMLElement> FindMatchingInputElement(HTMLDocument doc, string elementTagNameHint)
        {
            List<IHTMLElement> matchingFields = new List<IHTMLElement>();

            if (doc == null) return matchingFields;

            // allow all elements in doc with matching tag to be set, because some websites use layering.
            foreach (IHTMLElement element in doc.getElementsByTagName("input"))
            {
                if (ElementTagNameContainsHint(element, elementTagNameHint))
                {
                    matchingFields.Add(element);                    
                }
            }

            // If matching elements are not found in main document, try recursively looking into the frames.
            if (matchingFields.Count == 0)
            {
                for (int i = 0; i < doc.frames.length; i++)
                {
                    object index = (int)i;
                    HTMLWindow2Class frame = doc.frames.item(ref index) as HTMLWindow2Class;
                    HTMLDocument frameDoc = null;
                    if (frame != null)
                    {
                        try
                        {
                            frameDoc = frame.document as HTMLDocument;
                        }
                        catch (UnauthorizedAccessException)
                        {//TODO: nothing I can find to do about this.
                            frameDoc = null;
                        }
                        if (frameDoc != null)
                        {
                            // Recursive part.
                            return FindMatchingInputElement(frameDoc as HTMLDocument, elementTagNameHint);
                        }
                    }
                }
            }
            return matchingFields;
        }


        // Determine if an HTML Element's name or id contain the hint.
        private static bool ElementTagNameContainsHint(IHTMLElement element, string elementTagNameHint)
        {
            // check id for match.
            string tagName = element.getAttribute("id", 2) as string;
            System.Diagnostics.Debug.Write("\nElement ID:" + tagName);
            if (!String.IsNullOrEmpty(tagName) && tagName.ToLower(CultureInfo.CurrentCulture).Contains(elementTagNameHint))
                return true;

            // if not id, check name.
            tagName = element.getAttribute("name", 2) as string;
            System.Diagnostics.Debug.Write("Element Name:" + tagName);

            if (!String.IsNullOrEmpty(tagName) &&
                tagName.ToLower(CultureInfo.CurrentCulture).Contains(elementTagNameHint))
            {
                return true;
            }

            return false;

        }
        
        #endregion

    }
}
